#include <functional>
#include <cstring>
#include <string>

#include "memalloc_impl.h"
#include "memory_storage.h"
#include "disk_storage.h"
#include "../logger/logger.h"

using std::string;
using std::to_string;
using std::bind;
using std::function;
using std::placeholders::_1;

#define struct_entry(ptr, type, member) \
	((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))

MemAllocImpl::MemAllocImpl(uint64_t mem_size, uint64_t swap_size,
    const string& swap_path) {
	LOG_FUNCTION("");	
	pmem_ = new MemoryStorage(mem_size);
	pswap_ = new DiskStorage(swap_size, swap_path);
}

MemAllocImpl::~MemAllocImpl() {
	LOG_FUNCTION("");	
	for (auto pchunk : allocated_) 
		delete pchunk;
	delete pmem_;
	delete pswap_;
}

SmartPtrImpl *MemAllocImpl::Alloc(uint64_t size) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(size > 0);
	SmartPtrImpl *pret = new SmartPtrImpl(size, may_be_freed_.end(),
	    may_be_unswapped_.end());

	LOGGER_WRITE(Logger::Priority::INFO, "size = " + to_string(size));

	my_assert(pmem_);	
	
	pret->pmem_chunk_ = pmem_->Alloc(size, may_be_freed_,
	    bind(&MemAllocImpl::MoveChunkFromMemToSwap, this, _1));

	pret->imay_be_freed_ = may_be_freed_.insert(may_be_freed_.end(),
	    &(pret->pmem_chunk_));

	allocated_.insert(pret);

	my_assert(pret->pmem_chunk_);
	
	AssertOk();
	return pret;
}

void MemAllocImpl::MoveChunkFromMemToSwap(StorageChunk** to_move_chunk) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(to_move_chunk);
	SmartPtrImpl* pcur_impl = struct_entry(to_move_chunk, SmartPtrImpl, pmem_chunk_);
	my_assert(pcur_impl);
	my_assert(allocated_.find(pcur_impl) != allocated_.end());
	my_assert(pcur_impl->pmem_chunk_ == *to_move_chunk);

	CopyChunkFromMemToSwap(to_move_chunk);
	
	// check if it's possible to free this chunk from mem_ and mark as impossible
	my_assert(pcur_impl->imay_be_freed_ != may_be_freed_.end());
	may_be_freed_.erase(pcur_impl->imay_be_freed_);
	pcur_impl->imay_be_freed_ = may_be_freed_.end();

	pmem_->Free(pcur_impl->pmem_chunk_);
	pcur_impl->pmem_chunk_ = nullptr;
	AssertOk();
}


void MemAllocImpl::CopyChunkFromMemToSwap(StorageChunk** to_copy_chunk) {
	LOG_FUNCTION("");	
//	AssertOk();
	my_assert(to_copy_chunk);
	SmartPtrImpl* pcur_impl = struct_entry(to_copy_chunk, SmartPtrImpl, pmem_chunk_);
	my_assert(pcur_impl);
	my_assert(allocated_.find(pcur_impl) != allocated_.end());
	my_assert(pcur_impl->pmem_chunk_ == *to_copy_chunk);

	if (pcur_impl->imay_be_unswapped_ != may_be_unswapped_.end()) {
	// currently in swap
		may_be_unswapped_.erase(pcur_impl->imay_be_unswapped_);
		pcur_impl->imay_be_unswapped_ = may_be_unswapped_.end();
	}
	else 
		pcur_impl->pswap_chunk_ = pswap_->Alloc(pcur_impl->size_,
		    may_be_unswapped_,
		    bind(&MemAllocImpl::MoveChunkFromSwapToMem, this, _1));

	LOGGER_WRITE(Logger::Priority::INFO, "Before copy");

	void *src = pmem_->Load(pcur_impl->pmem_chunk_);
	my_assert(src);
	void *dest = pswap_->Load(pcur_impl->pswap_chunk_);
	my_assert(dest);
	memcpy(dest, src, pcur_impl->size_);
	LOGGER_WRITE(Logger::Priority::INFO, "After copy");
//	AssertOk();
}

void MemAllocImpl::MoveChunkFromSwapToMem(StorageChunk** to_move_chunk) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(to_move_chunk);
	SmartPtrImpl* pcur_impl = struct_entry(to_move_chunk, SmartPtrImpl, pswap_chunk_);
	my_assert(pcur_impl);
	my_assert(allocated_.find(pcur_impl) != allocated_.end());
	my_assert(pcur_impl->pswap_chunk_ == *to_move_chunk);

	CopyChunkFromSwapToMem(to_move_chunk);
	
	// check if it's possible to free this chunk from swap_ and mark as impossible
	my_assert(pcur_impl->imay_be_unswapped_ != may_be_unswapped_.end());
	may_be_unswapped_.erase(pcur_impl->imay_be_unswapped_);
	pcur_impl->imay_be_unswapped_ = may_be_unswapped_.end();

	pswap_->Free(pcur_impl->pswap_chunk_);
	pcur_impl->pswap_chunk_ = nullptr;
	AssertOk();
}

void MemAllocImpl::CopyChunkFromSwapToMem(StorageChunk** to_copy_chunk) {
	LOG_FUNCTION("");	
//	AssertOk();
	my_assert(to_copy_chunk);
	SmartPtrImpl* pcur_impl = struct_entry(to_copy_chunk, SmartPtrImpl, pswap_chunk_);
	my_assert(pcur_impl);
	my_assert(allocated_.find(pcur_impl) != allocated_.end());
	my_assert(pcur_impl->pswap_chunk_ == *to_copy_chunk);

	if (pcur_impl->imay_be_freed_ != may_be_freed_.end()) {
	// currently in mem
		may_be_freed_.erase(pcur_impl->imay_be_freed_);
		pcur_impl->imay_be_freed_ = may_be_freed_.end();
	}
	else 
		pcur_impl->pmem_chunk_ = pmem_->Alloc(pcur_impl->size_,
		    may_be_freed_,
		    bind(&MemAllocImpl::MoveChunkFromMemToSwap, this, _1));

	LOGGER_WRITE(Logger::Priority::INFO, "Before copy");

	void *src = pswap_->Load(pcur_impl->pswap_chunk_);
	my_assert(src);
	void *dest = pmem_->Load(pcur_impl->pmem_chunk_);
	my_assert(dest);
	LOGGER_WRITE(Logger::Priority::INFO, "Before memcpy");
	memcpy(dest, src, pcur_impl->size_);
}

void MemAllocImpl::Free(SmartPtrImpl *ptr) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(ptr);
	my_assert(allocated_.find(ptr) != allocated_.end());
	if (ptr->ref_counter_ >= 1) {
		// материться в логах
	}
	if (ptr->lock_counter_ >= 1) {
		// очень сильно материться в логах
	}

	allocated_.erase(ptr);
	if (ptr->imay_be_freed_ != may_be_freed_.end())
		may_be_freed_.erase(ptr->imay_be_freed_);
	if (ptr->imay_be_unswapped_ != may_be_unswapped_.end())
		may_be_unswapped_.erase(ptr->imay_be_unswapped_);

	if (ptr->pmem_chunk_) 
		pmem_->Free(ptr->pmem_chunk_);
	if (ptr->pswap_chunk_)
		pswap_->Free(ptr->pswap_chunk_);
	
	delete ptr;
	AssertOk();
}

void MemAllocImpl::AddRef(SmartPtrImpl *ptr) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(ptr);
	my_assert(allocated_.find(ptr) != allocated_.end());
	ptr->ref_counter_++;
	AssertOk();
}

void MemAllocImpl::DelRef(SmartPtrImpl *ptr) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(ptr);
	my_assert(allocated_.find(ptr) != allocated_.end());
	my_assert(ptr->ref_counter_ >= 1);
	ptr->ref_counter_--;
	if (!(ptr->ref_counter_))
		Free(ptr);
	AssertOk();
}

void *MemAllocImpl::Lock(SmartPtrImpl *ptr) {
	LOG_FUNCTION("");	
	AssertOk();//LESHCH ты б её под условную комниляцию убрал, что ли :)
	my_assert(ptr);
	my_assert(allocated_.find(ptr) != allocated_.end());
	ptr->lock_counter_++;//LESHCH у тебя же сюда могут попасть несколько потоков одновременно
	if (ptr->imay_be_freed_ != may_be_freed_.end()) {
		may_be_freed_.erase(ptr->imay_be_freed_);//LESHCH и сюда... :(
		ptr->imay_be_freed_ = may_be_freed_.end();
	}

	if (!(ptr->pmem_chunk_)) {
		my_assert(ptr->pswap_chunk_);
		CopyChunkFromSwapToMem(&(ptr->pswap_chunk_));
		// CopyChunkFromSwapToMem will add chunk to may_be_unswapped
	}
	
	void *ret = pmem_->Load(ptr->pmem_chunk_);
	my_assert(ret);
	AssertOk();
	return ret;
}

void MemAllocImpl::Release(SmartPtrImpl *ptr) {
	LOG_FUNCTION("");	
	AssertOk();
	my_assert(ptr);
	my_assert(allocated_.find(ptr) != allocated_.end());
	my_assert(ptr->lock_counter_ >= 1);
	ptr->lock_counter_--;

	if (!(ptr->lock_counter_))
		ptr->imay_be_freed_ = may_be_freed_.insert(
		    may_be_freed_.end(), &(ptr->pmem_chunk_));
	AssertOk();
}

void MemAllocImpl::AssertOk() { 
	my_assert(pmem_);
	my_assert(pswap_);	
	my_assert(pmem_ != pswap_);
	pmem_->AssertOk();
	pswap_->AssertOk();
	my_assert(may_be_freed_.size() <= allocated_.size());
	my_assert(may_be_unswapped_.size() <= allocated_.size());
	for (auto pimpl : allocated_) {
		my_assert(pimpl);
		my_assert(pimpl->size_ > 0);
//		my_assert(pimpl->ref_counter_ > 0);
		LOGGER_WRITE(Logger::Priority::INFO, "size_ = " + 
		    to_string(pimpl->size_));
		my_assert(pimpl->pswap_chunk_ || pimpl->pmem_chunk_);
		if (pimpl->pswap_chunk_) 
			my_assert(pswap_->Load(pimpl->pswap_chunk_)); // TODO: Exceptions check!
		if (pimpl->pmem_chunk_) 
			my_assert(pmem_->Load(pimpl->pmem_chunk_)); // TODO: Exceptions check!
		
		if (pimpl->pswap_chunk_ && pimpl->pmem_chunk_) {
//			my_assert(pimpl->imay_be_unswapped_ != may_be_unswapped_.end());
			if (pimpl->lock_counter_ == 0) {
				my_assert(pimpl->imay_be_freed_ != may_be_freed_.end());
			}
			else {
				my_assert(pimpl->imay_be_freed_ == may_be_freed_.end());
			}
		}
//		else {
//			my_assert(pimpl->imay_be_freed_ == may_be_freed_.end());
//			my_assert(pimpl->imay_be_unswapped_ == may_be_unswapped_.end());
//		}
	}
}

SmartPtrImpl::SmartPtrImpl(uint64_t size,
    const std::list<StorageChunk**>::iterator& imay_be_freed,
    const std::list<StorageChunk**>::iterator& imay_be_unswapped):
	size_(size),
	ref_counter_(0),
	lock_counter_(0),
	pswap_chunk_(nullptr),
	pmem_chunk_(nullptr),
	imay_be_freed_(imay_be_freed),
	imay_be_unswapped_(imay_be_unswapped) {
	LOG_FUNCTION("");	
}


